//---------------------------------------------------------------------------
//#include <d3dx9math.h>
#include "player_info.h"
#include "events.h"
//---------------------------------------------------------------------------
void (*pPrint)(int r,const char *text,...) = (void(*)(int,const char*,...))0x004b3800;

//CDHASH::CDHASH(){}
//
//CDHASH::CDHASH(CDHASH2 oldhash)
//{
//	for(size_t i=0; i<32; i++)
//	{
//		hash[i] = oldhash.hash[i];
//	}
//}
//bool operator<(const CDHASH left, const CDHASH right)
//{
//	for(size_t i=0; i<32; i++)
//	{
//		if(left.hash[i]<right.hash[i])
//			return true;
//		else if(left.hash[i]==right.hash[i])
//			continue;
//		else
//			return false;
//	}
//	return false;
//}

//---------------------------------------------------------------------------
PlayerManager* GManager;
PlayerManager* Manager()
{
	return GManager;
}
void SetManager(PlayerManager* manager)
{
	GManager = manager;
}

PlayerInfo::PlayerInfo()
{
	admin_powers = 0;

	// Handicaps/settings
	// handicaps are multipliers that are applied when player spawns
	handicap_shield = 1;
	handicap_health = 1;
	handicap_speed = 1;
	handicap_ammo = 1;
	handicap_clip = 1;
	can_chat = true;

	join_global = "";
	join_private = "";

	// tracking info
	botdetects = 0;

	last_team = RED;
}

void PlayerInfo::OnSpawn(playerindex my_ind)
{
	ABiped* mc = Server()->GetBiped(my_ind);
	//if (handicap_shield != 1)
	//	mc->Shield *= handicap_shield;
	if (handicap_health != 1)
		mc->Health *= handicap_health;
	if (handicap_speed != 1)
		Server()->GetPlayer(my_ind)->VelocityMultiplier = handicap_speed;
	/*if (handicap_ammo != 1)
		getWeap(my_ind)->Ammo = (int)(getWeap(my_ind)->Ammo * handicap_ammo);
	if (handicap_clip != 1)
		getWeap(my_ind)->Clip = (int)(getWeap(my_ind)->Clip * handicap_clip);*/
}

//---------------------------------------------------------------------------
// ** Player Manager **
//
//PlayerManager::PlayerManager()
//	//: kills(0), deaths(0), assists(0), team(0), wason(0)
//{
//
//}
PlayerManager::~PlayerManager()
{
	SavePlayers();
}
//---------------------------------------------------------------------------

void PlayerManager::SavePlayers()
{
	PlayerInfo defaultplayer;
	PlayerInfo* player;
	string hash;
	string filename;
	//char filepath[75];
	bool saveable = false;

	// don't bother making a directory, or just use system("mkdir ...") instead, or a batch file..
	//pPrint(0, "%d", GetCurrentDirectoryA(75, filepath));
	//CreateDirectoryA((filepath + players_dir).c_str(), NULL);

	filename = save_dir + "nicknames.ini";
	if(!CIniFile::Create(filename))
	{
		pPrint(0, "Nickname file creation failed. (Perhaps you didn't make the directories yet?)");
	}
	for(map<string, string>::iterator hash = nicknames.begin(); hash != nicknames.end(); hash++)
	{
		CIniFile::SetValue(hash->first, hash->second, "nicknames", filename);
	}

	for(map<string, PlayerInfo>::iterator info = Players.begin(); info != Players.end(); info++)
	{
		hash = info->first;
		player = &info->second;
		filename = players_dir + hash + players_ext;


		if(!CIniFile::Create(filename))
		{
			pPrint(0, "Player file creation failed for %s. (Perhaps you didn't make the directories yet?)", hash.c_str());
		}
		//CIniFile::AddSection("Misc", filename);
		//CIniFile::AddSection("Handicap", filename);
		//CIniFile::AddSection("JoinMsgs", filename);

		if(player->admin_powers != defaultplayer.admin_powers)
			CIniFile::SetValue("admin", ToStr(player->admin_powers), "Misc", filename);
		if(player->botdetects != defaultplayer.botdetects)
			CIniFile::SetValue("botdetects", ToStr(player->botdetects), "Misc", filename);
		if(player->can_chat != defaultplayer.can_chat)
			CIniFile::SetValue("can_chat", (player->can_chat?"true":"false"), "Misc", filename);
		if(player->handicap_shield != defaultplayer.handicap_shield)
			CIniFile::SetValue("shield", ToStr(player->handicap_shield), "Handicaps", filename);
		if(player->handicap_health != defaultplayer.handicap_health)
			CIniFile::SetValue("health", ToStr(player->handicap_health), "Handicaps", filename);
		if(player->handicap_speed != defaultplayer.handicap_speed)
			CIniFile::SetValue("speed", ToStr(player->handicap_speed), "Handicaps", filename);
		if(player->handicap_ammo != defaultplayer.handicap_ammo)
			CIniFile::SetValue("ammo", ToStr(player->handicap_ammo), "Handicaps", filename);
		if(player->handicap_clip != defaultplayer.handicap_clip)
			CIniFile::SetValue("clip", ToStr(player->handicap_clip), "Handicaps", filename);
		if(player->join_global != defaultplayer.join_global)
			CIniFile::SetValue("global", player->join_global, "JoinMsgs", filename);
		if(player->join_private != defaultplayer.join_private)
			CIniFile::SetValue("private", player->join_private, "JoinMsgs", filename);

	}
}
//---------------------------------------------------------------------------

void PlayerManager::LoadPlayers()
{
	string filename;
	string hash;
	char hashbuf[33];
	int temp;
	PlayerInfo* pl;
	vector<CIniFile::Record> info;

	// First, load nicknames.
	CIniFile::Load(save_dir + "nicknames.ini", info);
	for(size_t i=0; i<info.size(); i++)
	{
		nicknames[info[i].Key] = info[i].Value;
	}

	// Get a listing of all files in the players directory.
	// And, if the filename matches HASH.txt, try to INI load it.
	WIN32_FIND_DATAA filedata;
	HANDLE hand = FindFirstFileA((LPCSTR) (players_dir+"*"+players_ext).c_str(), &filedata);
	while(hand != INVALID_HANDLE_VALUE)
	{
		if(filename == filedata.cFileName)
		{
			pPrint(0, "Loaded players.");
			FindClose(hand);
			return;
		}
		filename = filedata.cFileName;
		//pPrint(0, filename.c_str());
		FindNextFileA(hand, &filedata);

		// and for each file successfully loaded, create a new PlayerInfo entry (and add it to the Players map).
		if(CIniFile::Load(players_dir + filename, info))
		{
		if(info.size() != 0)
		{
			sscanf(filename.c_str(), ("%32s"+players_ext).c_str(), hashbuf);
			hash = hashbuf;
			//pPrint(0, hash.c_str());
			if(hash.length() < 30) // that's probably not a valid playerfile.
				continue;

			pl = GetPlayerInfo(hash);
			for(size_t i=0; i<info.size(); i++)
			{
				if(info[i].Key == "admin")
				{
					sscanf(info[i].Value.c_str(), "%d", &temp);
					pl->admin_powers = temp;
				}
				else if(info[i].Key == "botdetects")
				{
					sscanf(info[i].Value.c_str(), "%d", &temp);
					pl->botdetects = temp;
				}
				else if(info[i].Key == "can_chat")
				{
					if(info[i].Value == "false")
						pl->can_chat = false;
					else
						pl->can_chat = true;
				}
				else if(info[i].Key == "ammo")
					sscanf(info[i].Value.c_str(), "%f", &pl->handicap_ammo);
				else if(info[i].Key == "clip")
					sscanf(info[i].Value.c_str(), "%f", &pl->handicap_clip);
				else if(info[i].Key == "shield")
					sscanf(info[i].Value.c_str(), "%f", &pl->handicap_shield);
				else if(info[i].Key == "health")
					sscanf(info[i].Value.c_str(), "%f", &pl->handicap_health);
				else if(info[i].Key == "speed")
					sscanf(info[i].Value.c_str(), "%f", &pl->handicap_speed);
				else if(info[i].Key == "global")
					pl->join_global = info[i].Value;
				else if(info[i].Key == "private")
					pl->join_private = info[i].Value;
			}

		}
		}
	}
	pPrint(0, "No files found in players directory.");

}

//---------------------------------------------------------------------------


PlayerInfo* PlayerManager::GetPlayerInfo(string hash)
{
	return &Players[hash];
}

PlayerInfo* PlayerManager::GetPlayerInfo(playerindex pl_ind, bool ignoreOff)
{
	if(!ignoreOff && !Server()->IsOn(pl_ind))
		return NULL;
	playerindex mach_ind = Server()->GetPlayer(pl_ind)->MachineIndex;
	string hash = Server()->machinelist[mach_ind].CDhash;
	return &Players[hash];
}

PlayerInfo* PlayerManager::GetInfoByNickname(string nickname)
{
	// <nickname, hash>
	map<string, string>::iterator pl = nicknames.find(nickname);
	if(pl == nicknames.end())
		return NULL;
	return &Players[pl->second];
}

Static_Player* PlayerManager::GetPlayerByNickname(string nickname)
{
	map<string, string>::iterator pl = nicknames.find(nickname);
	if(pl == nicknames.end())
		return NULL;
	for(playerindex i=0; i<16; i++)
	{
		if(Server()->machinelist[i].CDhash == pl->second)
			for(playerindex ind=0; ind<16; ind++)
				if(Server()->IsOn(ind) && Server()->GetPlayer(ind)->MachineIndex == i)
					return Server()->GetPlayer(ind);
	}
	return NULL;
}
void PlayerManager::AddNickname(playerindex pl_ind, string nickname)
{
	if(!Server()->IsOn(pl_ind))
		return;
	string hash = Server()->GetPlayerHash(pl_ind);
	nicknames[nickname] = hash;
}
//---------------------------------------------------------------------------
void CheckAim(playerindex ind);
void PlayerWatcher(void* param)
{
	// Initialization.
	InitCustomCommands();
	Static_Player* pl;
	Manager()->aim_sensitivity = 920;
	Manager()->botcheck = false;
	Manager()->zombification = false;
	Manager()->save_dir = "superapp\\";
	Manager()->players_dir = Manager()->save_dir + "players\\";
	Manager()->locations_dir = Manager()->save_dir + "locations\\";
	Manager()->save_ext = ".txt";
	Manager()->players_ext = ".ini";
	for(playerindex i=0; i<16; i++)
	{
		pl = Server()->GetPlayer(i);
		Manager()->kills[i]=pl->KillsCount;
		Manager()->deaths[i]=pl->DeathsCount;
		Manager()->dead[i]=false;
		Manager()->assists[i]=pl->AssistsCount;
		Manager()->team[i]=pl->Team;
		Manager()->wason[i]=Server()->IsOn(i);
	}
	Manager()->LoadPlayers();
	Manager()->teleport.Load();
	return;
	playerindex i;
	while(1)
	{
	for(i=0; i<16; i++)
	{
		pl = Server()->GetPlayer(i);

		if(Server()->IsOn(i)) {
			if(!Manager()->wason[i])
				Manager()->PlayerJoin(i);
		} else {
			if(Manager()->wason[i])
				Manager()->PlayerQuit(i);
			continue;
		}

		/*if(pl->Deaths > Manager()->deaths[i])
		{
			Manager()->OnDeath(i);
			//_beginthreadex(NULL, NULL, PlayerDeath, &i, NULL, NULL);
			//Sleep(1200);
		}*/
		if(Manager()->dead[i])
		{
			if(pl->RespawnTimer == 0)
			{
				Manager()->dead[i] = false;
				Manager()->PlayerSpawn(i);
			}
			if(Manager()->zombification)
			{
				if(Manager()->team[i] == RED) // if they're a human
				{
					Manager()->team[i] = Server()->GetPlayer(i)->Team; // they Can choose to go blue.
					//HumanCheck(i);
				} else // force zombies' teams.
					Server()->SetTeam(i, BLUE);
			}
		}


		//if(pl->DeathsCount < Manager()->deaths[i])
		//	Manager()->deaths[i] = pl->DeathsCount; // Hmm.. maybe a map reset. I'm not sure
		//if(pl->KillsCount < Manager()->kills[i])
		//	Manager()->kills[i] = pl->KillsCount;
		//if(pl->AssistsCount < Manager()->assists[i])
		//	Manager()->assists[i] = pl->AssistsCount;

		if(Manager()->botcheck && Server()->IsSpawned(i))
			CheckAim(i);
	}
	Sleep(0);
	}
}

// Temporarily misnamed - Is actually OnKilled (but killer is unknown)
void PlayerManager::OnDeath(Static_Player* pl)
{ // if this player died AND another player got a kill right here, it's probably not coincidence and the other player killed this player.

	//Static_Player* pl = Server()->GetPlayer(ind);
	playerindex ind = pl->PlayerIndex;
	Static_Player* opl;

	deaths[ind] = pl->DeathsCount;
	dead[ind] = true;
	pPrint(0, "%d died.", ind);

	if(zombification)
	{
		for(int i=0; i<16; i++)
		{
			if (!Server()->IsOn(i))
				continue;  // don't bother checking if its a nonexistant player.

			opl = Server()->GetPlayer(i);
			if (opl->Team == BLUE && opl->KillsCount > kills[i])
			{ // then this is probably the zombie who killed this human.
				kills[i] = opl->KillsCount;
				team[ind] = BLUE;
				pPrint(0, "%d zombified by %d", ind, i);
				break; // but this might be coincidental.. but who cares, since _A_ blue killed _A_ red.
			}
		}
	}
}

void PlayerManager::PlayerJoin(playerindex i)
{
	wason[i] = true;

	pPrint(0, "%s joined", Server()->GetPlayerName(i).c_str());

	if(false && GetPlayerInfo(i)->admin_powers > 0)
	{
		string msg;
		if(GetPlayerInfo(i)->admin_powers > 9000)
			msg = "sv_say \"Admin: " + Server()->GetPlayerName(i) + ", leve.. Wait, what?! IT'S OVER NINE THOUSAND!!!: " + ToStr(GetPlayerInfo(i)->admin_powers) + "\"";
		else
			msg = "sv_say \"Admin: " + Server()->GetPlayerName(i) + ", level " + ToStr(GetPlayerInfo(i)->admin_powers) + "\"";
		Server()->ExecCmd(msg.c_str());
	}
	if(zombification)
		Server()->SetTeam(i, GetPlayerInfo(i)->last_team);

}

void PlayerManager::PlayerQuit(playerindex i)
{
	pPrint(0, "%d quit", i);
	GetPlayerInfo(i, true)->last_team = team[i];
	wason[i] = false;

}

void PlayerManager::PlayerSpawn(playerindex i)
{
	pPrint(0, "%d spawned", i);
	GetPlayerInfo(i)->OnSpawn(i);
}

#pragma comment(lib, "d3dx9.lib")

// **** AIMBOT detection ****
//#define cross D3DXVec3Cross
#define dot D3DXVec3Dot
#define normalize D3DXVec3Normalize
bool SnappedToPlayer(playerindex i, float anglediff);
void CheckAim(playerindex i)
{
	// With the current position And direction of aim for the player,
	// check if it's aimed at any other spawned player. (not This player though, but all others.)
	static vect3 rotations[16] = {BAD_LOC};
	vect3 look_normal;
	vect3 prev_normal;
	float diff_angle;
	for(playerindex i=0; i<16; i++)
	{
		if(Server()->IsSpawned(i))
		{
			if(rotations[i] == BAD_LOC)
			{
				rotations[i] = Server()->GetBiped(i)->RealLookVect;
				continue;
			}
			D3DXVec3Normalize(&look_normal, &Server()->GetBiped(i)->RealLookVect);
			D3DXVec3Normalize(&prev_normal, &rotations[i]);

			diff_angle = acos( dot( &look_normal, &prev_normal));
			//pPrint(0, "diff: %f", diff_angle);

			if(diff_angle > 0.58f) // that was a somewhat large snap... Check if they're now aiming at a player.
			{
				if(Server()->debug)
					pPrint(0, "Player %d snapped", i);
				SnappedToPlayer(i, diff_angle);
			}
			rotations[i] = Server()->GetBiped(i)->RealLookVect;

		}
		else // clear that player's recorded rotation.
			rotations[i] = BAD_LOC;
	}
}

bool SnappedToPlayer(playerindex i, float anglediff)
{
	ABiped* me = Server()->GetBiped(i);
	ABiped* mc;

	D3DXVECTOR3 selfcoord = me->World;
	float self_yaw = Server()->GetMachine(i)->Yaw;
	float self_pitch = Server()->GetMachine(i)->Pitch;
	D3DXVECTOR3 coord;
	D3DXVECTOR3 relative_coord;
	float yaw_relative;
	float pitch_relative;
	float yaw_diff;
	float pitch_diff;
	float dist;

//	pPrint(0, "%d look: %f,%f,%f", i, look_normal.x, look_normal.y, look_normal.z);
	for(playerindex ind=0; ind<16; ind++)
	{
		if (ind == i)
			continue;
		if(!Server()->IsSpawned(ind))
			continue;
		mc = Server()->GetBiped(ind);
		coord = mc->World;
		relative_coord = coord - selfcoord;
		dist = Dist3D(relative_coord);

		yaw_relative = atan2f(relative_coord.y, relative_coord.x);
		pitch_relative = asinf(relative_coord.z / dist);

		yaw_diff = yaw_relative - self_yaw;
		if(yaw_diff > PI)
			yaw_diff -= 2*PI;
		if(yaw_diff < -PI)
			yaw_diff += 2*PI;

		ABSVALUE(yaw_diff)

		pitch_diff = pitch_relative - self_pitch;

		if(Server()->debug)
			pPrint(0, "%d rel yaw/pitch: %f,%f  my yaw/pitch: %f,%f, diff yaw/pitch: %f,%f", ind, yaw_relative, pitch_relative, self_yaw, self_pitch, yaw_diff, pitch_diff);

		// A larger snap or distance makes the accuracy to player less necessary.
		if(yaw_diff < (dist * anglediff / Manager()->aim_sensitivity ) )
		{
			if(pitch_diff < dist * anglediff / (Manager()->aim_sensitivity * 0.92)) // and pitch is less reliable than yaw.
				return true;//OnBotDetect(i, ind);
				//Events()->OnEvent("botdetect", player->bot_detects, i, ind);
		}
	}
	return false;
}

// Detection successful. If it's the first detection for that player, ignore it. Second: give warning. Third: sv_kick or similar.
// **MAYBE**: have the things done to the player be User configurable in its own. (w/ my params of course)
// OR, integrate it with the events_ system. "event_detectasbot #" ( event_detect 3 'say $number "That's it, you're out."')
//OnDetect(){
	// They snapped onto a player. Get their profile info, increment their BotDetect count, and do something.

	//string msg = "sv_say \" Bot detect: " + Server()->GetPlayerName(pl_ind) + " snapped to " + Server()->GetPlayerName(target) + "!\"";
	//Server()->ExecCmd(msg.c_str());



//---------------------------------------------------------------------------

// **** ZOMBIES stuff*****

void PlayerManager::ZombieReset()
{
	char rand = ((unsigned char)Server()->GetPlayer(2)->Name[3] % 16);
	rand = ((unsigned char)Server()->GetPlayer(4)->Name[3] % 16);
	// some random number, from 0 to 15 :)

	for (playerindex i=0; i<16; i++)
	{
		kills[i] = 0;
		deaths[i] = 0;
		if(i == rand)
		{
			rand = 20; // a check
			Server()->SetTeam(i, BLUE);
			team[i] = BLUE;
		}else
		{
			team[i] = RED;
			Server()->SetTeam(i, RED);
		}
	}
	if(rand != 20) { // if the rand failed, just force the first player who's on.
		for (int i=0; i<16; i++) {
			if(Server()->IsOn(i))	{
				Server()->SetTeam(i, BLUE);
				break;
			}
		}
	}
	// Reset the map, so now all kills, deaths, scores, etc are 0. (Confirmed)
	Server()->ExecCmd("sv_map_reset");
	// *ON NEW ZOMBIEMATCH event();
}
/*
// Loop thread, that on a kill (someones deaths increases), it checks if it was a blue killing a red.
void Zombificator()
{
	zombie.zombification = true;
	// First, start a new match. that means make everyone red BUT one person, and then watch their team.
	ZombieReset();

	while(zombie.zombification)
	{
		for(int i=0; i<16; i++)
		{
			if(IsOn(i) && !zombie.wasOn[i])
			{	// Player joined.
				zombie.wasOn[i] = true;
			}
			if (!IsOn(i) && zombie.wasOn[i])
			{	// Player left, so reset personal records.
				zombie.wasOn[i] = false;
				zombie.playerkills[i] = 0;
				zombie.playerdeaths[i] = 0;
				zombie.playerteam[i] = RED;
				continue;
			}
			// Blue stays blue.
			if(zombie.playerteam[i])
				SetTeam(i, BLUE);

			if(zombie.playerteam[i] == RED) // if they're a human
			{
				zombie.playerteam[i] = GetPlayer(i)->Team; // they Can choose to go blue.
				HumanCheck(i);
			}
		}
		Sleep(20);
	} 

}
void ServerControl::HumanCheck(playerindex ind)
{ // if this player died AND another player got a kill right here, it's probably not coincidence and the other player killed this player.
	// A: check if the

	Static_Player* pl = GetPlayer(ind);

	if(pl->Deaths < zombie.playerdeaths[ind])
	{ // odd.. it went down. I will guess, then, that a mapreset happened.
		zombie.playerdeaths[ind] = pl->Deaths;

	} else if (pl->Deaths > zombie.playerdeaths[ind])
	{ // they died recently
		zombie.playerdeaths[ind] = pl->Deaths;
		for(int i=0; i<16; i++)
		{
			if (!IsOn(i))
				continue;  // don't bother if its a nonexistant player.

			if (GetPlayer(i)->Team == BLUE && GetPlayer(i)->Kills > zombie.playerkills[i])
			{ // then this is probably the zombie who killed this human.
				zombie.playerkills[i] = GetPlayer(i)->Kills;
				zombie.playerteam[ind] = BLUE;
			}
		}
	}
}

*/